using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace _3DTicTacToe._3D
{
    sealed class Cube : ModelVisual3D
    {
        private readonly GeometryModel3D _geoModel;
        private readonly Transform3DGroup _trGroup;

        public static readonly DependencyProperty MaterialProperty =
            GeometryModel3D.MaterialProperty.AddOwner(typeof (Cube), new PropertyMetadata(null, MaterialChanged));

        public static readonly DependencyProperty SideLengthProperty =
            DependencyProperty.Register("SideLength", typeof (double), typeof (Cube),
                                        new PropertyMetadata(1D, SideLengthChanged));

        public static readonly DependencyProperty PositionProperty =
            DependencyProperty.Register("Position", typeof(Point3D), typeof(Cube),
                                        new PropertyMetadata(new Point3D(0,0,0), PositionChanged));

        public Point3D Position
        {
            get { return (Point3D)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }

        public double SideLength
        {
            get { return (double)GetValue(SideLengthProperty); }
            set { SetValue(SideLengthProperty, value); }
        }

        public Material Material
        {
            get { return (Material)GetValue(MaterialProperty); }
            set { SetValue(MaterialProperty, value); }
        }

        public Cube()
            :this(1, new Point3D(0,0,0))
        {
        }

        public Cube(double sideLength, Point3D position)
        {
            _geoModel = new GeometryModel3D(GenerateGeometry(), null);
            _trGroup = new Transform3DGroup();

            _geoModel.Transform = _trGroup;
            Content = _geoModel;

            SideLength = sideLength;
            Position = position;
        }

        private static void PositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var cube = (Cube)d;

            var vector = (Point3D)e.NewValue - (Point3D)e.OldValue;
            var translate = new TranslateTransform3D(vector);

            cube._trGroup.Children.Add(translate);
        }

        private static void SideLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var cube = (Cube)d;

            var scFactor = 1 / ((double)e.OldValue / (double)e.NewValue);
            var scale = new ScaleTransform3D(new Vector3D(scFactor, scFactor, scFactor), cube.Position);

            cube._trGroup.Children.Add(scale);
        }

        private static void MaterialChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var cube = (Cube)d;

            if (e.Property == MaterialProperty)
                cube._geoModel.Material = e.NewValue as Material;
        }

        private static MeshGeometry3D GenerateGeometry()
        {
            var p1 = new Point3D(-0.5, 0.5, 0.5);
            var p2 = new Point3D(0.5, 0.5, 0.5);
            var p3 = new Point3D(0.5, 0.5, -0.5);
            var p4 = new Point3D(-0.5, 0.5, -0.5);
            var p5 = new Point3D(-0.5, -0.5, 0.5);
            var p6 = new Point3D(0.5, -0.5, 0.5);
            var p7 = new Point3D(0.5, -0.5, -0.5);
            var p8 = new Point3D(-0.5, -0.5, -0.5);

            var mesh = new MeshGeometry3D
                           {
                               Positions = new Point3DCollection(new[] { p1, p2, p3, p4, p5, p6, p7, p8 }),
                               TriangleIndices = new Int32Collection(new[]
                                                                         {
                                                                             3,2,6,
                                                                             3,6,7,
                                                                             2,1,5,
                                                                             2,5,6,
                                                                             1,0,4,
                                                                             1,4,5,
                                                                             0,3,7,
                                                                             0,7,4,
                                                                             7,6,5,
                                                                             7,5,4,
                                                                             2,3,0,
                                                                             2,0,1
                                                                         })
                           };
            return mesh;
        }
    }
}